<?php
// +----------------------------------------------------------------------
// | ZengCMS [ 火火 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2018 http://zengcms.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 火火 <zengcms@qq.com>
// +----------------------------------------------------------------------

// +----------------------------------------------------------------------
// | Auth扩展类
// +----------------------------------------------------------------------
namespace app\api\lib;

use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Builder;
use app\api\lib\OpensslAES;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\ValidationData;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use app\api\lib\exception\MissException;
include_once APP_PATH . '/api/lib/jwt/vendor/autoload.php';
/**
 * jwt类引入JWT，TOKEN三个部分组成，头部、有效负荷、签名
 * 
 * 单例一次请求中所有出现使用jwt的地方都是一个用户
 * 获取jwt的token令牌：Auth::getInstance()->setDataFromString($string = '')->encode()->getToken();//设置用户数据data(字符串即数组转json并OpensslAES加密的字符串)
 * 获取jwt的token令牌：Auth::getInstance()->setDataFromArr($data = [])->encode()->getToken();//设置用户数据data(数组)
 * 
 * 验证jwt的token令牌
 * $jwt = Auth::getInstance()->setToken('xxx')->decode();//xxx为token令牌
 * $res = $jwt->validate();//检查token前两部分即头部header和负荷playload
 * $res = $jwt->verify();//检查token最后一部分即signature签名
 * 
 * 获取jwt的token令牌中的信息
 * $data = $jwt->getData();//获取用户数据(即用户数组数据转json并OpensslAES加密的数据)
 * $data = $jwt->getDataStringByKey($key = '');//获取用户数据(字符串)
 * $data = $jwt->getDataArrByKey($key = []);//获取用户数据(数组)
 */
class Auth
{
    // 单例模式，Jwt句柄
    private static $instance = null;
    // Jwt token
    private $token;
    // jwt token有效期，单位秒
    private $exp = 120;
    // jwt refresh_token有效期，单位秒
    private $rtime = 30 * 24 * 60 * 60;
    // decode token
    private $decodeToken;
    // 用户数组数据转json并加密的数据
    private $data;
    // claim iss签发者
    private $iss = 'api.zengcms.com';
    // claim aud
    private $aud = 'zengcms_server.app';
    // claim jti
    private $jti = '4f1g23a12aa';
    // secrect
    private $secrect = 's3439758479$^%#$%#&%&**';
    // 获取Jwt的句柄，统一的入口
    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    // 私有化构造函数
    private function __construct(){}
    // 私有化clone函数
    private function __clone(){}
    // 设置用户数据data(字符串即数组转json并OpensslAES加密的字符串)
    public function setDataFromString($string = '')
    {
        if (empty($string)) {
            throw new MissException([
                'message' => 'token中数据不能为空！',
                'httpCode' => 400
            ]);
        }
        // 对数据string进行OpensslAES解密
        $str = (new OpensslAES())->decrypt($string);
        // 判断数据是否合法
        if (empty($str)) {
            throw new MissException([
                'message' => 'refresh_token数据不合法！',
                'httpCode' => 401
            ]);
        }
        $arr = json_decode($str, true);
        if ($arr['ip'] != get_reluser_ip()) {
            throw new MissException([
                'message' => 'refresh_token异常！',
                'httpCode' => 401
            ]);
        }
        if (time() - $arr['time'] > $this->rtime) {
            throw new MissException([
                'message' => 'refresh_token异常！',
                'httpCode' => 401
            ]);
        }
        $arr['time'] = time();
        $string = (new OpensslAES())->encrypt(json_encode($arr));
        $this->data = $string;
        return $this;
    }
    // 设置用户数据data(数组)
    public function setDataFromArr($data = [])
    {
        if (empty($data)) {
            throw new MissException([
                'message' => 'Token中数据不能为空！',
                'httpCode' => 400
            ]);
        }
        $this->data = (new OpensslAES())->encrypt(json_encode($data));
        return $this;
    }
    // 编码Jwt的token
    public function encode()
    {
        $time = time();
        $signer = new Sha256();
        $this->token = (new Builder())->issuedBy($this->iss)
        ->permittedFor($this->aud)
        ->identifiedBy($this->jti, true)
        ->issuedAt($time)
        ->canOnlyBeUsedAfter($time + 0)
        ->expiresAt($time + $this->exp)
        ->withClaim('data', $this->data)
        ->getToken($signer, new Key($this->secrect));
        return $this;
    }
    // 获取token
    public function getToken()
    {
        return (string) $this->token;
    }
    // 设置token
    public function setToken($token)
    {
        $this->token = $token;
        // 链式调用
        return $this; 
    }
    // 解码Jwt的token
    public function decode()
    {
        if (!$this->decodeToken) {
            $this->decodeToken = (new Parser())->parse((string) $this->token);
            $this->data = $this->decodeToken->getClaim('data');
        }
        return $this;
    }
    // validate校验token合法性(检查token前两部分即头部header和负荷playload)
    public function validate()
    {
        $time = time();
        $data = new ValidationData();
        $data->setIssuer($this->iss);
        $data->setAudience($this->aud);
        $data->setId($this->jti);
        $data->setCurrentTime($time + 0);
        return $this->decodeToken->validate($data);
    }
    // verify校验token合法性(检查token最后一部分即signature签名)
    public function verify()
    {
        return $this->decodeToken->verify(new Sha256(), $this->secrect);
    }
    // 获取用户数据(即用户数组数据转json并加密的数据)
    public function getData()
    {
        return $this->data;
    }
    // 获取用户数据(字符串)
    public function getDataStringByKey($key = '')
    {
        if (empty($key)) {
            throw new MissException([
                'message' => 'key不能为空！',
                'httpCode' => 400
            ]);
        }
        $data = (new OpensslAES())->decrypt($this->data);
        if (empty($data)) {
            throw new MissException([
                'message' => 'token中数据不合法！',
                'httpCode' => 400
            ]);
        }
        $data = json_decode($data, true);
        if (isset($data[$key])) {
            return $data[$key];
        } else {
            throw new MissException([
                'message' => '未找到key对应的值！',
                'httpCode' => 400
            ]);
        }
    }
    // 获取用户数据(数组)
    public function getDataArrByKey($key = [])
    {
        $data = (new OpensslAES())->decrypt($this->data);
        if (empty($data)) {
            throw new MissException([
                'message' => 'token不合法！',
                'httpCode' => 400
            ]);
        }
        $data = json_decode($data, true);
        if (empty($key)) {
            return $data;
        }
        $result = [];
        foreach ($key as $k) {
            if (array_key_exists($k, $data)) {
                $result[$k] = $data[$k];
            }
        }
        if (count($result) !== count($key)) {
            throw new MissException([
                'message' => '尝试获取的token中数据个别不存在！',
            ]);
        }
        return $result;
    }
}
